import { ElementRef, Injectable, Renderer2 } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class svgPanService {

  private enablePan = 1; // 1 or 0: enable or disable panning (default enabled)
  private enableZoom = 1; // 1 or 0: enable or disable zooming (default enabled)
  private enableDrag = 0; // 1 or 0: enable or disable dragging (default disabled)
  private root ;

  private state = 'none';
  private svgRoot;
  private stateTarget;
  private stateOrigin;
  private stateTf;

  constructor(
    // protected elementRef: ElementRef,
    // protected renderer: Renderer2
  ){
  }

  init(root){
    this.root = root;
    // this.svgRoot = root;
    // this.setupHandlers(this.root);
  }

  /**
   * Register handlers
   */
  setupHandlers(root){
    this.setAttributes(root, {
      "onmouseup" : this.handleMouseUp,
      "onmousedown" : this.handleMouseDown,
      "onmousemove" : this.handleMouseMove,
      //"onmouseout" : "handleMouseUp(evt)", // Decomment this to stop the pan functionality when dragging out of the SVG element
    });

    if(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0)
      window.addEventListener('mousewheel', this.handleMouseWheel, false); // Chrome/Safari
    else
      window.addEventListener('DOMMouseScroll', this.handleMouseWheel, false); // Others
  }

  /**
   * Retrieves the root element for SVG manipulation. The element is then cached into the svgRoot global letiable.
   */
  getRoot(root) {
    if(typeof(this.svgRoot) == "undefined") {
      let g = null;

      g = root.getElementById("viewport");

      if(g == null)
        g = root.getElementsByTagName('g')[0];

      if(g == null)
        alert('Unable to obtain SVG root element');

      this.setCTM(g, g.getCTM());

      g.removeAttribute("viewBox");

      this.svgRoot = g;
    }

    return this.svgRoot;
  }

  /**
   * Instance an SVGPoint object with given event coordinates.
   */
  getEventPoint(evt) {
    let p = this.root.createSVGPoint();

    p.x = evt.clientX;
    p.y = evt.clientY;

    return p;
  }

  /**
   * Sets the current transform matrix of an element.
   */
  setCTM(element, matrix) {
    let s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";

    element.setAttribute("transform", s);
  }

  /**
   * Dumps a matrix to a string (useful for debug).
   */
  dumpMatrix(matrix) {
    let s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\n  " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\n  0, 0, 1 ]";

    return s;
  }

  /**
   * Sets attributes of an element.
   */
  setAttributes(element, attributes){
    for (let i in attributes)
      element.setAttributeNS(null, i, attributes[i]);
  }

  /**
   * Handle mouse wheel event.
   */
  handleMouseWheel(evt) {
    if(!this.enableZoom)
      return;

    if(evt.preventDefault)
      evt.preventDefault();

    evt.returnValue = false;

    let svgDoc = evt.target.ownerDocument;

    let delta;

    if(evt.wheelDelta)
      delta = evt.wheelDelta / 3600; // Chrome/Safari
    else
      delta = evt.detail / -90; // Mozilla

    let z = 1 + delta; // Zoom factor: 0.9/1.1

    let g = this.getRoot(svgDoc);

    let p = this.getEventPoint(evt);

    p = p.matrixTransform(g.getCTM().inverse());

    // Compute new scale matrix in current mouse position
    let k = this.root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);

    this.setCTM(g, g.getCTM().multiply(k));

    if(typeof(this.stateTf) == "undefined")
      this.stateTf = g.getCTM().inverse();

    this.stateTf = this.stateTf.multiply(k.inverse());
  }

  /**
   * Handle mouse move event.
   */
  handleMouseMove(evt) {
    if(evt.preventDefault)
      evt.preventDefault();

    evt.returnValue = false;

    let svgDoc = evt.target.ownerDocument;

    let g = this.getRoot(svgDoc);

    if(this.state == 'pan' && this.enablePan) {
      // Pan mode
      let p = this.getEventPoint(evt).matrixTransform(this.stateTf);

      this.setCTM(g, this.stateTf.inverse().translate(p.x - this.stateOrigin.x, p.y - this.stateOrigin.y));
    } else if(this.state == 'drag' && this.enableDrag) {
      // Drag mode
      let p = this.getEventPoint(evt).matrixTransform(g.getCTM().inverse());

      this.setCTM(this.stateTarget, this.root.createSVGMatrix().translate(p.x - this.stateOrigin.x, p.y - this.stateOrigin.y).multiply(g.getCTM().inverse()).multiply(this.stateTarget.getCTM()));

      this.stateOrigin = p;
    }
  }

  /**
   * Handle click event.
   */
  handleMouseDown(evt) {
    if(evt.preventDefault)
      evt.preventDefault();

    evt.returnValue = false;

    let svgDoc = evt.target.ownerDocument;

    let g = this.getRoot(svgDoc);

    if(
      evt.target.tagName == "svg"
      || !this.enableDrag // Pan anyway when drag is disabled and the user clicked on an element
    ) {
      // Pan mode
      this.state = 'pan';

      this.stateTf = g.getCTM().inverse();

      this.stateOrigin = this.getEventPoint(evt).matrixTransform(this.stateTf);
    } else {
      // Drag mode
      this.state = 'drag';

      this.stateTarget = evt.target;

      this.stateTf = g.getCTM().inverse();

      this.stateOrigin = this.getEventPoint(evt).matrixTransform(this.stateTf);
    }
  }

  /**
   * Handle mouse button release event.
   */
  handleMouseUp(evt) {
    if(evt.preventDefault)
      evt.preventDefault();

    evt.returnValue = false;

    let svgDoc = evt.target.ownerDocument;

    if(this.state == 'pan' || this.state == 'drag') {
      // Quit pan mode
      this.state = '';
    }
  }

}



